package com.qiangesoft.bootcodegen.utils;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.config.ConstVal;
import com.baomidou.mybatisplus.generator.util.FileUtils;
import com.qiangesoft.bootcodegen.constant.CodegenConstant;
import com.qiangesoft.bootcodegen.entity.BcgTableColumn;
import com.qiangesoft.bootcodegen.framework.config.CodeGenParam;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 代码生成工具类
 *
 * @author qiangesoft
 * @date 2023-09-29
 */
public class CodeGenUtil {

    /**
     * 路径分隔符
     */
    public static final String FILE_SEP = "/";

    /**
     * java路径
     */
    public static final String JAVA_PATH = FILE_SEP + "src" + FILE_SEP + "main" + FILE_SEP + "java";

    /**
     * 资源文件路径
     */
    public static final String RESOURCES_PATH = FILE_SEP + "src" + FILE_SEP + "main" + FILE_SEP + "resources";

    /**
     * 控制层
     */
    public static String CONTROLLER = "controller";

    /**
     * 实体类
     */
    public static String ENTITY = "entity";

    /**
     * 服务层接口
     */
    public static String SERVICE = "service";

    /**
     * 服务层实现类
     */
    public static String SERVICE_IMPL = "serviceImpl";

    /**
     * 持久层
     */
    public static String MAPPER = "mapper";

    /**
     * 持久层xml
     */
    public static String MAPPER_XML = "mapperXml";

    /**
     * 查询对象
     */
    public static String QUERY = "query";

    /**
     * 传输对象
     */
    public static String DTO = "dto";

    /**
     * 对象转换
     */
    public static String CONVERT = "convert";

    /**
     * 视图对象
     */
    public static String VO = "vo";

    /**
     * vue页面
     */
    public static String PAGE = "page";

    /**
     * js代码
     */
    public static String API_JS = "api";

    /**
     * 模板文件
     */
    public static final Map<String, String> TEMPLATE_MAP = new HashMap<>();

    /**
     * 模板文件位置
     */
    static {
        TEMPLATE_MAP.put(CONTROLLER, FILE_SEP + "templates" + FILE_SEP + "controller.java.vm");
        TEMPLATE_MAP.put(ENTITY, FILE_SEP + "templates" + FILE_SEP + "entity.java.vm");
        TEMPLATE_MAP.put(SERVICE, FILE_SEP + "templates" + FILE_SEP + "service.java.vm");
        TEMPLATE_MAP.put(SERVICE_IMPL, FILE_SEP + "templates" + FILE_SEP + "serviceImpl.java.vm");
        TEMPLATE_MAP.put(MAPPER, FILE_SEP + "templates" + FILE_SEP + "mapper.java.vm");
        TEMPLATE_MAP.put(QUERY, FILE_SEP + "templates" + FILE_SEP + "Query.java.vm");
        TEMPLATE_MAP.put(DTO, FILE_SEP + "templates" + FILE_SEP + "DTO.java.vm");
        TEMPLATE_MAP.put(VO, FILE_SEP + "templates" + FILE_SEP + "VO.java.vm");
        TEMPLATE_MAP.put(CONVERT, FILE_SEP + "templates" + FILE_SEP + "Convert.java.vm");
        TEMPLATE_MAP.put(MAPPER_XML, FILE_SEP + "templates" + FILE_SEP + "mapper.xml.vm");
        TEMPLATE_MAP.put(PAGE, FILE_SEP + "templates" + FILE_SEP + "page.vue.vm");
        TEMPLATE_MAP.put(API_JS, FILE_SEP + "templates" + FILE_SEP + "api.js.vm");
    }

    /**
     * 代码存放路径
     */
    public static Map<String, String> codeGenFilePath(String genModule, String genPackage) {
        Map<String, String> codeGenFilePackage = codeGenFilePackage(genModule, genPackage);

        Map<String, String> map = new HashMap<>();
        String controllerPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(CONTROLLER).replace(".", FILE_SEP) + FILE_SEP;
        String entityPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(ENTITY).replace(".", FILE_SEP) + FILE_SEP;
        String servicePath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(SERVICE).replace(".", FILE_SEP) + FILE_SEP;
        String serviceImplPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(SERVICE_IMPL).replace(".", FILE_SEP) + FILE_SEP;
        String mapperPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(MAPPER).replace(".", FILE_SEP) + FILE_SEP;
        String queryPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(QUERY).replace(".", FILE_SEP) + FILE_SEP;
        String dtoPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(DTO).replace(".", FILE_SEP) + FILE_SEP;
        String voPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(VO).replace(".", FILE_SEP) + FILE_SEP;
        String convertPath = JAVA_PATH + FILE_SEP + codeGenFilePackage.get(CONVERT).replace(".", FILE_SEP) + FILE_SEP;
        String mapperXmlPath = RESOURCES_PATH + FILE_SEP + "mapper" + FILE_SEP + (StringUtils.isNotBlank(genModule) ? genModule + FILE_SEP : "");
        String pagePath = FILE_SEP + "page" + FILE_SEP + "views" + FILE_SEP;
        String apiJsPath = FILE_SEP + "page" + FILE_SEP + "api" + FILE_SEP;
        map.put(CONTROLLER, controllerPath);
        map.put(ENTITY, entityPath);
        map.put(SERVICE, servicePath);
        map.put(SERVICE_IMPL, serviceImplPath);
        map.put(MAPPER, mapperPath);
        map.put(QUERY, queryPath);
        map.put(DTO, dtoPath);
        map.put(VO, voPath);
        map.put(CONVERT, convertPath);
        map.put(MAPPER_XML, mapperXmlPath);
        map.put(PAGE, pagePath);
        map.put(API_JS, apiJsPath);
        return map;
    }

    /**
     * java代码包路径
     */
    public static Map<String, String> codeGenFilePackage(String genModule, String genPackage) {
        Map<String, String> map = new HashMap<>();
        String packagePath = StringUtils.isNotBlank(genModule) ? genPackage + "." + genModule : genPackage;
        String controllerPath = packagePath + ".controller";
        String entityPath = packagePath + ".entity";
        String servicePath = packagePath + ".service";
        String serviceImplPath = servicePath + ".impl";
        String mapperPath = packagePath + ".mapper";
        String queryPath = packagePath + ".pojo.query";
        String dtoPath = packagePath + ".pojo.dto";
        String voPath = packagePath + ".pojo.vo";
        String convertPath = packagePath + ".pojo.convert";
        map.put(CONTROLLER, controllerPath);
        map.put(ENTITY, entityPath);
        map.put(SERVICE, servicePath);
        map.put(SERVICE_IMPL, serviceImplPath);
        map.put(MAPPER, mapperPath);
        map.put(QUERY, queryPath);
        map.put(DTO, dtoPath);
        map.put(VO, voPath);
        map.put(CONVERT, convertPath);
        return map;
    }

    /**
     * 本地项目生成
     */
    public static void genLocal(CodeGenParam codeGenParam) throws IOException {
        // 1.初始化Velocity引擎
        initVelocity();

        // 2.加载velocity容器
        VelocityContext velocityContext = buildVelocityContext(codeGenParam);

        // 3.生成文件
        String genClass = codeGenParam.getGenClass();
        Map<String, String> map = codeGenFilePath(codeGenParam.getGenModule(), codeGenParam.getGenPackage());
        for (String key : map.keySet()) {
            String templatePath = TEMPLATE_MAP.get(key);
            String filePath = map.get(key);
            String fileName = getFileName(genClass, key, templatePath, filePath);
            File file = new File(codeGenParam.getGenPath() + fileName);
            boolean exist = file.exists();
            if (!exist) {
                File parentFile = file.getParentFile();
                FileUtils.forceMkdir(parentFile);
            }
            Template template = Velocity.getTemplate(templatePath, ConstVal.UTF8);
            try (FileOutputStream fos = new FileOutputStream(file);
                 OutputStreamWriter ow = new OutputStreamWriter(fos, ConstVal.UTF8);
                 BufferedWriter writer = new BufferedWriter(ow)) {
                template.merge(velocityContext, writer);
            } catch (Exception e) {
                throw new RuntimeException("代码生成失败");
            }
        }
    }

    /**
     * 下载代码
     *
     * @param codeGenParam
     * @param response
     */
    public static void genDownload(CodeGenParam codeGenParam, HttpServletResponse response) {
        // 1.初始化Velocity引擎
        initVelocity();

        // 2.加载velocity容器
        VelocityContext velocityContext = buildVelocityContext(codeGenParam);

        // 3.生成文件
        String genClass = codeGenParam.getGenClass();
        Map<String, String> map = codeGenFilePath(codeGenParam.getGenModule(), codeGenParam.getGenPackage());
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
            for (String key : map.keySet()) {
                String templatePath = TEMPLATE_MAP.get(key);
                String filePath = map.get(key);
                String fileName = getFileName(genClass, key, templatePath, filePath).replaceFirst("/", "");
                creatZipEntry(templatePath, fileName, velocityContext, zipOutputStream);
            }
            IOUtils.closeQuietly(zipOutputStream);
            byte[] bytes = outputStream.toByteArray();
            response.reset();
            response.setHeader("Content-Disposition", "attachment; filename=code.zip");
            response.addHeader("Content-Length", "" + bytes.length);
            response.setContentType("application/octet-stream; charset=UTF-8");
            IOUtils.write(bytes, response.getOutputStream());
        } catch (Exception e) {
            throw new RuntimeException("代码生成失败");
        }
    }

    /**
     * 初始化vm
     */
    private static void initVelocity() {
        Properties properties = new Properties();
        properties.setProperty(ConstVal.VM_LOAD_PATH_KEY, ConstVal.VM_LOAD_PATH_VALUE);
        properties.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, StringPool.EMPTY);
        properties.setProperty(Velocity.ENCODING_DEFAULT, ConstVal.UTF8);
        properties.setProperty(Velocity.INPUT_ENCODING, ConstVal.UTF8);
        properties.setProperty("file.resource.loader.unicode", StringPool.TRUE);

        Velocity.init(properties);
    }

    /**
     * 变量替换参数
     *
     * @param codeGenParam
     * @return
     */
    private static VelocityContext buildVelocityContext(CodeGenParam codeGenParam) {
        VelocityContext velocityContext = new VelocityContext();
        velocityContext.put("tableName", codeGenParam.getTableName());
        velocityContext.put("genAuthor", codeGenParam.getGenAuthor());
        velocityContext.put("genDate", codeGenParam.getGenDate());
        velocityContext.put("genFunction", codeGenParam.getGenFunction());
        velocityContext.put("genClass", codeGenParam.getGenClass());
        char firstChar = codeGenParam.getGenClass().charAt(0);
        velocityContext.put("genClassCamel", Character.toLowerCase(firstChar) + codeGenParam.getGenClass().substring(1));
        List<BcgTableColumn> columnList = codeGenParam.getColumnList();
        velocityContext.put("columnList", columnList);
        velocityContext.put("controllerMapping", TableColumnInitUtil.camelToChar(codeGenParam.getGenClass(), '-'));
        velocityContext.put("packageConfig", codeGenFilePackage(codeGenParam.getGenModule(), codeGenParam.getGenPackage()));
        velocityContext.put("columnConfig", CodegenConstant.COLUMN_CONFIG);
        for (BcgTableColumn tableColumn : columnList) {
            if (tableColumn.getColumnPk()) {
                velocityContext.put("idConfig", tableColumn);
                break;
            }
        }

        velocityContext.put("hutoolStrUtil", cn.hutool.core.util.StrUtil.class);
        return velocityContext;
    }

    /**
     * 文件信息
     *
     * @param genClass
     * @param key
     * @param templatePath
     * @param filePath
     * @return
     */
    private static String getFileName(String genClass, String key, String templatePath, String filePath) {
        String fileBaseName = templatePath.substring(templatePath.lastIndexOf('/') + 1, templatePath.lastIndexOf('.'));
        String fileName;
        if (ENTITY.equals(key)) {
            fileName = filePath + genClass + ".java";
        } else if (PAGE.equals(key)) {
            fileName = filePath + genClass + ".vue";
        } else if (API_JS.equals(key)) {
            fileName = filePath + genClass + ".js";
        } else if (SERVICE.equals(key)) {
            fileName = filePath + "I" + genClass + Character.toUpperCase(fileBaseName.charAt(0)) + fileBaseName.substring(1);
        } else {
            fileName = filePath + genClass + Character.toUpperCase(fileBaseName.charAt(0)) + fileBaseName.substring(1);
        }
        return fileName;
    }

    /**
     * 合成单个文件
     *
     * @param templatePath
     * @param fileName
     * @param velocityContext
     * @param zipOutputStream
     */
    private static void creatZipEntry(String templatePath, String fileName, VelocityContext velocityContext, ZipOutputStream zipOutputStream) throws IOException {
        // 加载模板并替换变量
        try (StringWriter stringWriter = new StringWriter()) {
            Template template = Velocity.getTemplate(templatePath, ConstVal.UTF8);
            template.merge(velocityContext, stringWriter);
            zipOutputStream.putNextEntry(new ZipEntry(fileName));
            String content = stringWriter.toString();
            zipOutputStream.write(content.getBytes(ConstVal.UTF8));
            zipOutputStream.flush();
            zipOutputStream.closeEntry();
        }
    }

}
